<template>
  <div class="image-uploader">
    <!-- 图片列表 -->
    <div
      v-for="(item, index) in list"
      :key="index"
      class="image__container"
    >
      <!-- 图片 -->
      <el-image ref="image" class="image" :src="item" :preview-src-list="list" />

      <!-- 操作按钮 -->
      <div class="image__actions">
        <span class="action-item" @click="previewImage($event, index)">
          <i class="action-item__icon el-icon-zoom-in" />
        </span>
        <span v-if="!mixedProps.disabled" class="action-item" @click="removeImage(index)">
          <i class="action-item__icon el-icon-delete" />
        </span>
      </div>
    </div>

    <!-- 加载中内容 -->
    <div
      v-if="loading"
      v-loading="true"
      class="image__container"
      :element-loading-text="loading.text || '加载中'"
    >
      <el-image class="image" :src="loading.image" />
    </div>

    <!-- 上传按钮 -->
    <el-upload
      v-if="canUpload"
      class="uploader"
      action=""
      :class="{ 'disabled': mixedProps.disabled }"
      :multiple="false"
      :show-file-list="false"
      :http-request="uploader"
      :before-upload="beforeUploadHandler"
    >
      <i class="uploader__icon el-icon-plus" />
    </el-upload>
  </div>
</template>

<script>
import imageCompression from 'browser-image-compression'

// 图片文件类型正则
const FILE_TYPE_REG = /^image\/(jpg|jpeg|png)$/
// 图片压缩配置
const IMAGE_COMPRESSION_CONFIG = {
  maxSizeMB: 1,
  maxWidthOrHeight: 1920,
  useWebWorker: true
}

export default {
  name: 'ImageUploader',

  inject: ['elForm'],

  props: {
    value: {
      type: Array,
      default() {
        return []
      }
    },

    count: {
      type: Number,
      default: 1,
      validator(val) {
        return val > 0
      }
    },

    service: {
      type: Function
    },

    path: {
      type: String,
      default: ''
    },

    disabled: {
      type: Boolean,
      default: false
    },

    // 禁用时是否展示添加按钮
    showAddWhenDisabled: {
      type: Boolean,
      default: true
    }
  },

  data() {
    return {
      loading: null
    }
  },

  computed: {
    // 图片列表
    list: {
      get() {
        return Array.isArray(this.value) ? this.value : []
      },
      set(val) {
        this.$emit('input', Array.isArray(val) ? val : [])
      }
    },

    length() {
      let length = this.list.length
      if (this.loading) {
        length++
      }
      return length
    },

    // 限制数量
    limit() {
      return this.count >= 1 ? this.count : 1
    },

    // 合并的 Props
    mixedProps() {
      const disabled = !!(this.disabled || (this.elForm && this.elForm.disabled))

      return {
        disabled: !!disabled
      }
    },

    // 是否可上传
    canUpload() {
      if (this.length >= this.limit) {
        return false
      } else if (this.mixedProps.disabled) {
        if (this.showAddWhenDisabled) {
          return !(this.length > 0)
        } else {
          return false
        }
      } else {
        return true
      }
    }
  },

  methods: {
    /**
     * 放大预览图片
     * @param {Event} event 事件对象
     * @param {number} index 图片序号
     */
    previewImage(event, index) {
      if (index >= 0) {
        const $images = this.$refs['image']
        const $image = $images ? $images[index] : undefined
        if ($image && typeof $image.clickHandler === 'function') {
          $image.clickHandler(event)
        }
      }
    },
    /**
     * 移除图片
     * @param {number} index 图片序号
     */
    removeImage(index) {
      if (index >= 0) {
        const list = [...this.list]
        list.splice(index, 1)
        this.list = list
      }
    },

    async uploader({ file }) {
      if (!(file instanceof Blob)) {
        this.$message.error('图片选择异常')
        return Promise.reject('图片选择异常')
      }
      if (typeof this.service !== 'function') {
        this.$message.error('图片上传异常')
        return Promise.reject('未定义图片上传方法')
      }

      const fileUrl = URL.createObjectURL(file)

      // 展示压缩状态提示
      this.loading = { image: fileUrl, text: '压缩中' }

      // 图片压缩
      let compressedFile
      try {
        compressedFile = await imageCompression(file, IMAGE_COMPRESSION_CONFIG)
      } catch (error) {
        console.error('图片压缩异常：', error)
        this.$message.error('图片压缩失败')
        return Promise.reject(error)
      } finally {
        this.loading = null
      }

      // 展示上传状态提示
      this.loading = { image: fileUrl, text: '上传中' }

      // 图片上传
      try {
        const [data] = await this.service({ file: compressedFile, path: this.path })
        this.list = [...this.list, data]
      } catch (error) {
        console.error('图片上传异常：', error)
        this.$message.error('图片上传失败')
        return Promise.reject(error)
      } finally {
        this.loading = null
      }
    },

    /**
     * 上传前处理方法
     * @param {File} file 上传的文件
     */
    beforeUploadHandler(file) {
      if (!file || !FILE_TYPE_REG.test(file.type)) {
        this.$message.error('请选择 JPG 或 PNG 格式的图片')
        return false
      }

      return true
    }
  }
}
</script>

<style lang="scss" scoped>
.image-uploader {
  $size: 100px;

  display: flex;
  flex-wrap: wrap;
  line-height: 1.15;

  .image {
    display: block;
    width: $size;
    height: $size;

    &__container {
      position: relative;
      margin-bottom: 10px;

      &:not(:last-child) {
        margin-right: 10px;
      }
    }

    &__actions {
      position: absolute;
      top: 50%;
      left: 50%;
      display: flex;
      transform: translate(-50%, -50%);

      .action-item {
        $btn-size: 30px;

        display: flex;
        align-items: center;
        justify-content: center;
        width: $btn-size;
        height: $btn-size;
        cursor: pointer;
        background-color: rgba(0, 0, 0, 0.4);
        border-radius: $btn-size;

        &:not(:last-child) {
          margin-right: 10px;
        }

        &__icon {
          color: white;
        }
      }
    }
  }

  .uploader {
    margin-bottom: 10px;
    font-size: 0;
    line-height: 0;

    &:not(:last-child) {
      margin-right: 10px;
    }

    ::v-deep .el-upload {
      position: relative;
      overflow: hidden;
      cursor: pointer;
      border: 1px dashed #d9d9d9;
      border-radius: 6px;

      &:hover {
        border-color: #409eff;
      }
    }

    &.disabled {
      cursor: not-allowed;

      ::v-deep .el-upload {
        pointer-events: none;
      }
    }

    &__icon {
      width: $size;
      height: $size;
      font-size: 28px;
      line-height: $size;
      color: #8c939d;
      text-align: center;
    }
  }
}
</style>
